/**
 * 
 */
package demo.applet;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.net.URLDecoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.regex.Pattern;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;

import demo.net.AllowAllHostnameVerifier;
import demo.net.FakeX509TrustManager;
import demo.util.BaseUtils;
import demo.util.Encoding;

/**
 * @author Roger Xu
 * 
 */
public class YouTubeDownloaderApplet extends JApplet {

    private static final long serialVersionUID = -1322689962528189304L;

    private static final boolean USE_PROXY = true;
    private static final Proxy PROXY_HTTP = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8123));
    private static final String UA_STRING_BROWER = "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.861.0 Safari/535.2 AppEngine-Google;";
    private static final String UA_STRING_MOBILE = "Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16";
    private static SSLSocketFactory SSL_SOCKET_FACTORY = null;
    private static final HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER = new AllowAllHostnameVerifier();

    static {
        // create fake SSL socket factory
        try {
            final TrustManager fakeTrustManager = new FakeX509TrustManager();
            final SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, new TrustManager[] { fakeTrustManager }, new SecureRandom());
            SSL_SOCKET_FACTORY = sc.getSocketFactory();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }

    }

    enum VideoFormat {
        UNKNOWN(-1, "Unknown"), _3GP_LOW(13, "3GP 144p"), _3GP_MED(17, "3GP 144p"), _3GP_HIGH(36, "3GP 240p"), FLV_LOW(
                5, "FLV 240p"), FLV_MED(34, "FLV 360p"), FLV_MED2(6, "FLV 360p"), FLV_HIGH(35, "FLV 480p"), MP4_HIGH(
                18, "MP4 480p"), MP4_HD(22, "MP4 720p"), MP4_HD2(37, "MP4 1080P"), MP4_HD3(38, "MP4 Original"), WebM_360P(
                43, "WebM 360p"), WebM_480P(44, "WebM 480p"), WebM_720P(45, "WebM 720p");

        private int id = 0;
        private String name = null;

        private VideoFormat(final int id, final String name) {
            this.id = id;
            this.name = name;
        }

        int getId() {
            return this.id;
        }

        @Override
        public String toString() {
            return this.name;
        }
    }

    private final JTextPane outputPane = new JTextPane();
    private final JTextArea outputArea = new JTextArea();

    @Override
    public void init() {
        try {
            SwingUtilities.invokeAndWait(new Runnable() {

                @Override
                public void run() {
                    createGUI();
                }
            });
        } catch (Exception e) {
            System.err.println("createGUI didn't complete successfully");
        }
    }

    private void createGUI() {

        // controls
        final JLabel urlLabel = new JLabel("URL:");
        final JTextField urlTextField = new JTextField(30);
        String videoUrl = "http://www.youtube.com/watch?v=5YXVMCHG-Nk";
        videoUrl = "http://www.youtube.com/watch?v=3yqM--IMkX4";
        urlTextField.setText(videoUrl);
        final JButton submitButton = new JButton("Submit");
        outputPane.setContentType("text/html; charset=UTF-8");
        final JButton clearButton = new JButton("Clear");

        // input panel
        final JPanel inputPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
        inputPanel.add(urlLabel);
        inputPanel.add(urlTextField);
        inputPanel.add(submitButton);

        // Initialize some styles.
        final StyledDocument doc = outputPane.getStyledDocument();
        final Style def = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
        final Style regular = doc.addStyle("regular", def);
        final Style s = doc.addStyle("title", regular);
        StyleConstants.setBold(s, true);
        StyleConstants.setForeground(s, Color.BLUE);
        StyleConstants.setFontSize(s, 16);

        // scroll pane
        final JScrollPane outputScrollPane = new JScrollPane(outputPane);
        outputScrollPane.setPreferredSize(new Dimension(400, 300));
        final JScrollPane logScrollPane = new JScrollPane(outputArea);
        final JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, outputScrollPane, logScrollPane);

        // bottom panel
        final JPanel buttonBarPanel = new JPanel(new GridLayout(1, 3));
        buttonBarPanel.add(clearButton);

        // events
        submitButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                final String url = urlTextField.getText();
                appendLine(url);

                // start fetch
                fetch(url);
            }
        });
        clearButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                outputPane.setText("");
                outputArea.setText("");
            }
        });

        // main panel
        final JPanel mainPanel = new JPanel(new BorderLayout());
        mainPanel.add(inputPanel, BorderLayout.NORTH);
        mainPanel.add(splitPane, BorderLayout.CENTER);
        mainPanel.add(buttonBarPanel, BorderLayout.SOUTH);

        this.setContentPane(mainPanel);

        this.setSize(600, 400);
    }

    protected void fetch(String uParam) {
        if (uParam.contains("youtu.be/")) {
            uParam = uParam.replace("youtu.be/", "www.youtube.com/watch?v=");
        }

        final String url = uParam + "&fmt=18";
        appendLine("fetch page source from: " + url);
        String pageSource = getUrlContent(url, "GET", "");

        // video id
        String videoId = StringUtils.substringBetween(pageSource, "shortlink\" href=\"http://youtu.be/", "\"");
        appendLine("1.1. get video_id from shortlink: " + videoId);
        if (videoId == null) {
            videoId = StringUtils.substringBetween(pageSource, "'VIDEO_ID': \"", "\"");
            appendLine("1.2. get video_id from VIDEO_ID: " + videoId);
        }
        if (videoId == null) {
            videoId = StringUtils.substringBetween(url, "v=", "&");
            appendLine("1.3. get video_id from url: " + videoId);
            pageSource = getUrlContent("http://www.youtube.com/get_video_info?video_id=" + videoId
                    + "&asv=3&el=detailpage&hl=en_US", "GET", "");
        }

        appendLine("Video ID: " + videoId);

        // title
        appendLine("Getting Title...");
        String title = StringUtils.substringBetween(pageSource, "'VIDEO_TITLE': '", "',");
        appendLine("2.1. get video_title from VIDEO_TITLE: " + title);
        if (title == null) {
            title = StringUtils.substringBetween(pageSource, "name=\"title\" content=\"", "\"");
            appendLine("2.2. get video_title from name: " + title);
        }
        if (title == null) {
            try {
                title = URLDecoder.decode(StringUtils.substringBetween(pageSource, "&title=", "&"), Encoding.UTF8)
                        .replace("+", " ");
                appendLine("2.3. get video_title from &title: " + title);
            } catch (UnsupportedEncodingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        title = StringEscapeUtils.unescapeHtml4(title);
        appendLine("Title: " + title);
        appendHtml("Title: " + title, "title");

        // fmt_url
        String fmtUrlMap = null;
        try {
            fmtUrlMap = URLDecoder.decode(StringUtils.substringBetween(pageSource, "fmt_stream_map=", "&"),
                    Encoding.UTF8);
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        if (fmtUrlMap == null) {
            fmtUrlMap = StringUtils.substringBetween(pageSource, "fmt_stream_map\": \"", "\"").replace("\\/", "/");
        }

        // parse video formats
        final String[] fmtUrlEntries = fmtUrlMap.split(",");
        for (final String fmtUrlEntry : fmtUrlEntries) {
            String fmtUrl = StringUtils.removeStart(fmtUrlEntry, "url=");
            String clipUrl = null;
            try {
                String[] parts = fmtUrl.split(Pattern.quote("&quality"));
                clipUrl = URLDecoder.decode(parts[0], "UTF-8");
            } catch (UnsupportedEncodingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            final VideoFormat videoFormat = getVideoFormat(clipUrl);

            // output
            final String downloadUrl = clipUrl + "&title=" + title;
            appendLine(videoFormat.toString() + ": " + downloadUrl);
            appendHtml(videoFormat.toString() + ": " + downloadUrl, null);
        }
    }

    private void appendHtml(final String line, final String style) {
        final StyledDocument doc = outputPane.getStyledDocument();
        try {
            doc.insertString(doc.getLength(), line, style == null ? null : doc.getStyle(style));
            doc.insertString(doc.getLength(), "\n", null);
        } catch (BadLocationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private void appendLine(final String line) {
        outputArea.append(line);
        outputArea.append("\n");
    }

    private VideoFormat getVideoFormat(final String url) {
        final String regexp = "itag=(\\d+)(?!\\d)";
        final String result = BaseUtils.extractString(url, regexp);
        final int fmtId = Integer.parseInt(result);

        VideoFormat videoFormat = VideoFormat.UNKNOWN;
        for (VideoFormat format : VideoFormat.values()) {
            if (fmtId == format.getId()) {
                videoFormat = format;
                break;
            }
        }

        return videoFormat;
    }

    private String getUrlContent(final String urlAddress, final String method, final String enc) {
        String responseBody = null;

        try {
            final URL url = new URL(urlAddress);
            HttpURLConnection conn = null;
            if (url.getProtocol() == "https") {
                HttpsURLConnection httpsConn = (HttpsURLConnection) url.openConnection(USE_PROXY ? PROXY_HTTP
                        : Proxy.NO_PROXY);
                httpsConn.setSSLSocketFactory(SSL_SOCKET_FACTORY); // set customized SSL setting
                httpsConn.setHostnameVerifier(ALLOW_ALL_HOSTNAME_VERIFIER);
                conn = httpsConn;
            } else {
                conn = (HttpURLConnection) url.openConnection(USE_PROXY ? PROXY_HTTP : Proxy.NO_PROXY);
            }

            // set request property
            if (urlAddress.contains("layout=mobile")) {
                conn.setRequestProperty("User-Agent", UA_STRING_MOBILE);
            } else {
                conn.setRequestProperty("User-Agent", UA_STRING_BROWER);
            }

            // connect
            conn.setRequestMethod(method);
            conn.setConnectTimeout((int) (5 * DateUtils.MILLIS_PER_SECOND));
            conn.connect();

            // input stream
            final InputStream in = conn.getInputStream();
            try {
                final String encoding = StringUtils.defaultIfBlank(enc, Encoding.UTF8);
                responseBody = IOUtils.toString(in, encoding);
            } finally {
                conn.disconnect();
            }
        } catch (Exception e) {
            e.printStackTrace();
            try {
                // System.err.println(e);
                // getAppletContext().showDocument(new URL("javascript:jerror();"));
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        return responseBody;
    }
}
